package com.jzsk.variable.center.common.utils.buildjar;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import com.jzsk.variable.center.common.utils.buildjar.ClassInfo;
import lombok.extern.slf4j.Slf4j;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

@Slf4j
public class JarUtils {

    public JarUtils() {
    }

    //项目spring boot jar包路径
    public static final String PROJECT_JAR_FILE = "project.jar.file";

    //编译文件生成路径
    private static final String COMPILE_JAVA_FILE_PATH = "/data/log/rms-variable-center-zhengxin/compile_java_file/";
    private static final String COMPILE_LIB = "/data/log/rms-variable-center-zhengxin/compile_lib/";

    private static final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    private static final String JAVA_SUFFIX = ".java";


    static {
        if (javaCompiler == null) {
            log.error("ToolProvider.getSystemJavaCompiler() is null.");
        }
    }

    /**
     * 复制class文件到待打包目录
     *
     * @param classInfoList 待打包的class文件信息
     */
    public static void copyClassToCompile(List<ClassInfo> classInfoList) throws IOException {
        if (CollectionUtil.isEmpty(classInfoList)) {
            return;
        }
        for (ClassInfo classInfo : classInfoList) {
            File file = new File(COMPILE_JAVA_FILE_PATH + packagePath(classInfo.getClassName()));
            if (!file.exists()) {
                file.mkdirs();
            }
            File clazzFile = new File(file.getPath() + File.separator + classInfo.getClazz().getName());
            clazzFile.createNewFile();
            FileInputStream fis = new FileInputStream(classInfo.getClazz());
            FileUtil.writeFromStream(fis, clazzFile);
        }
    }

    public static void buildJar(String jarName) {
        //生成jar包
        exportJar(jarName);
        //删除临时文件
        deleteTempFile();
    }

    /**
     * 编译指定的所有java文件
     *
     * @param collect      需要包含待编译的类文件
     * @param dependJarSet 编译需要用到的第三方依赖jar。会去到spring boot打包时默认的lib下获取，如需手动指定需要调整代码
     * @return 为null时表示成功，非null时包含编译出错时的错误信息，可直接转成字符串打印
     * @throws IOException 异常
     */
    public static ByteArrayOutputStream compileFile(Collection<JavaCompilerInfo> collect, Set<String> dependJarSet) throws IOException {
        String extraJars = getDependJars(dependJarSet);
        String[] args;
        int i = 2;
        if (extraJars != null && !extraJars.isEmpty()) {
            args = new String[collect.size() + 4];
            args[2] = "-classpath";
            args[3] = extraJars;
            i = 4;
        } else {
            args = new String[collect.size() + 2];
        }
        // 设置编码，避免编码问题
        args[0] = "-encoding";
        args[1] = "utf-8";

        for (JavaCompilerInfo info : collect) {
            File file = new File(COMPILE_JAVA_FILE_PATH + packagePath(info.getFullPathClassName()));
            if (!file.exists()) {
                file.mkdirs();
            }
            file = new File(file.getPath() + File.separator + getFileName(info.getFullPathClassName()));
            file.createNewFile();
            FileUtil.writeString(info.getJavaStr(), file, StandardCharsets.UTF_8);
            args[i] = file.getPath();
            i++;
        }
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        int result = javaCompiler.run(null, null, byteOutputStream, args);
        if (result == 0) {
            return null;
        }
        return byteOutputStream;
    }

    /**
     * 根据java类的全路径名获取对应的包路径
     *
     * @param fullPathClassName java类的全路径名
     * @return 包路径
     */
    private static String packagePath(String fullPathClassName) {
        if (fullPathClassName.endsWith(JAVA_SUFFIX)) {
            return fullPathClassName.substring(0, fullPathClassName.lastIndexOf("."))
                    .substring(0, fullPathClassName.lastIndexOf("."))
                    .replace(".", File.separator) + File.separator;
        }
        return fullPathClassName.substring(0, fullPathClassName.lastIndexOf("."))
                .replace(".", File.separator) + File.separator;
    }

    /**
     * 从java类的全路径获取java文件名
     *
     * @param fullPathClassName com.jzsk.variable..zxtzys.Feature
     * @return java文件名
     */
    private static String getFileName(String fullPathClassName) {
        if (fullPathClassName.endsWith(JAVA_SUFFIX)) {
            return fullPathClassName.substring(0, fullPathClassName.length() - JAVA_SUFFIX.length())
                    .substring(fullPathClassName.lastIndexOf(".") + 1) + JAVA_SUFFIX;
        }
        return fullPathClassName.substring(fullPathClassName.lastIndexOf(".") + 1) + JAVA_SUFFIX;
    }

    /**
     * 从项目jar包中获取依赖的jar包并复制一份到编译lib目录
     *
     * @param dependJarSet 依赖的jar
     * @return 复制后的所有jar包绝对路径拼接
     * @throws IOException 异常
     */
    private static String getDependJars(Set<String> dependJarSet) throws IOException {
        if (CollectionUtil.isEmpty(dependJarSet)) {
            return null;
        }
        JarFile jarFile = getJarFile();
        if (jarFile == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (String jarName : dependJarSet) {
            if (jarName.trim().isEmpty()) {
                continue;
            }
            String absolutePath = copyJarToCompileLib(jarFile, jarName);
            if (absolutePath != null) {
                sb.append(absolutePath);
            }
        }
        return sb.toString();
    }

    /**
     * 从spring boot jar包的BOOT-INF/lib/下复制依赖的jar包到编译lib目录
     *
     * @param jarFile spring boot jar包
     * @param jarName 依赖的jar包，如：fastjson-1.2.47.jar
     * @return 复制后的文件路径
     */
    private static String copyJarToCompileLib(JarFile jarFile, String jarName) throws IOException {
        File libFile = new File(COMPILE_LIB);
        if (!libFile.exists()) {
            libFile.mkdirs();
        }
        File destination = new File(COMPILE_LIB + jarName);
        if (destination.exists() && destination.length() > 0) {
            return destination.getAbsolutePath() + File.pathSeparator;
        }
        JarEntry jarEntry = jarFile.getJarEntry("BOOT-INF/lib/" + jarName);
        if (jarEntry == null) {
            log.error("jar file not found. jarName:{}", jarName);
            return null;
        }

        try (InputStream is = jarFile.getInputStream(jarEntry);
             FileOutputStream fos = new FileOutputStream(destination)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        } catch (Exception e) {
            log.error("copyJarToCompileLib fail. jarName:{}", jarName, e);
            return null;
        }
        return destination.getAbsolutePath() + File.pathSeparator;
    }

    /**
     * 根据环境变量project.jar.file设定的值加载项目jar文件
     *
     * @return 项目jar文件
     * @throws IOException 异常
     */
    private static JarFile getJarFile() throws IOException {
        String projectJarFilePath = System.getProperty(PROJECT_JAR_FILE);
        File file = new File(projectJarFilePath);
        if (file.exists()) {
            return new JarFile(file);
        }
        return null;
    }

    /************************************** 生成jar包 ******************************************/

    /**
     * 导出jar包
     *
     * @param outPath 输出jar包文件路径
     * @return jar包文件
     */
    public static File exportJar(String outPath) {
        return exportJar(COMPILE_JAVA_FILE_PATH, COMPILE_LIB, outPath);
    }

    /**
     * 导出jar包
     *
     * @param classFilePath 类文件根目录
     * @param libPath       lib文件目录
     * @param outPath       输出jar包文件路径
     * @return jar包文件
     */
    public static File exportJar(String classFilePath, String libPath, String outPath) {
        try {
            File outFile = new File(outPath);
            if (!outFile.exists()) {
                outFile.createNewFile();
            }
            JarOutputStream out = new JarOutputStream(Files.newOutputStream(outFile.toPath()), getManifest());
            packageClassFile(out, new File(classFilePath), "");
            packageLibFile(out, new File(libPath));
            out.flush();
            out.close();
            return outFile;
        } catch (Exception e) {
            log.error("jar包文件生成失败");
        }
        return null;
    }

    /**
     * 创建临时jar包
     *
     * @param out  jar包输出流
     * @param f    待打包的类文件根路径
     * @param base 相对层级
     * @throws IOException
     */
    private static void packageClassFile(JarOutputStream out, File f, String base) throws IOException {
        if (f.isDirectory()) {
            File[] fl = f.listFiles();
            if (fl != null && fl.length > 0) {
                if (base.length() > 0) {
                    base = base + "/";
                }
                for (File file : fl) {
                    packageClassFile(out, file, base + file.getName());
                }
            }
        } else {
            if (base.endsWith(".class")) {
                out.putNextEntry(new JarEntry(base));
                try (FileInputStream in = new FileInputStream(f)) {
                    byte[] buffer = new byte[1024];
                    int n = in.read(buffer);
                    while (n != -1) {
                        out.write(buffer, 0, n);
                        n = in.read(buffer);
                    }
                }
            }
        }
    }

    /**
     * 将依赖的jar包输出到待打包的jar包文件中
     *
     * @param jos  待打包的jar包文件
     * @param file lib包目录
     */
    private static void packageLibFile(JarOutputStream jos, File file) throws IOException {
        File[] libFiles = file.listFiles();
        if (libFiles == null || libFiles.length == 0) {
            return;
        }
        byte[] buffer = new byte[1024];
        int bytesRead;
        for (File libFile : libFiles) {
            if (libFile.getName().endsWith(".jar")) {
                jos.putNextEntry(new JarEntry("lib/" + libFile.getName()));
                InputStream is = new FileInputStream(libFile);
                while ((bytesRead = is.read(buffer)) != -1) {
                    jos.write(buffer, 0, bytesRead);
                }
                is.close();
            }
        }
    }

    public static void deleteTempFile() {
        FileUtil.del(new File(COMPILE_JAVA_FILE_PATH));
    }

    private static Manifest getManifest() {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
        return manifest;
    }


}
